goog.provide('dkpoints.auth');
goog.provide('dkpoints.auth.Auth');

goog.require('dkpoints.auth.AuthState');
goog.require('dkpoints.rpc.RPCService');
goog.require('dkpoints.rpc.commands.AuthCheckCommand');
goog.require('dkpoints.rpc.commands.AuthCheckResponse');
goog.require('dkpoints.rpc.commands.LogoutCommand');
goog.require('dkpoints.status.Status');
goog.require('dkpoints.utils');



/**
 * The singleton Auth object is used to track the user's authentication status.
 * @param {dkpoints.rpc.RPCService} rpcService The RPC service we need to make
 *    auth checks.
 * @param {dkpoints.menu.Menu} menu The Menu instance for the app.  Needed to
 *    update the authorization-specific features.
 * @param {dkpoints.status.Status} status The Status panel for the app.
 * @constructor
 */
dkpoints.auth.Auth = function(rpcService, menu, status) {
  /**
   * @type {dkpoints.rpc.RPCService}
   * @private
   */
  this.rpc_ = rpcService;

  /**
   * @type {dkpoints.menu.Menu}
   * @private
   */
  this.menu_ = menu;

  /**
   * @type {dkpoints.status.Status}
   * @private
   */
  this.status_ = status;

  /**
   * @type {dkpoints.nav.Nav}
   * @private
   */
  this.nav_ = null;

  /**
   * @type {dkpoints.rpc.commands.AuthCheckResponse}
   * @private
   */
  this.authStatus_ = null;

  /**
   * @type {Array.<function(dkpoints.rpc.commands.AuthCheckResponse)>}
   * @private
   */
  this.listenerCallbacks_ = [];

  /**
   * @type {boolean}
   * @private
   */
  this.waitingForResponse_ = false;
};


/**
 * Sets the nav manager as a permanent listener.
 * @param {dkpoints.nav.Nav} nav The nav manager.
 */
dkpoints.auth.Auth.prototype.setNavManager = function(nav) {
  this.navManager_ = nav;
};


/**
 * When the Auth object is first created, immediately check the user's auth
 * status and set up the menu.  The callback from checkStatus will always set
 * the menu's status, so we don't need a callback of our own.
 */
dkpoints.auth.Auth.prototype.init = function() {
  this.checkStatus();
};


/**
 * This method forces a trip to the server to see what the user's auth status
 * is.
 * @param {function(dkpoints.rpc.commands.AuthCheckResponse)=} callback
 *    Callback to the requester, regardless of success or failure.
 */
dkpoints.auth.Auth.prototype.checkStatus = function(callback) {
  // If someone's asked us to check, make sure we don't return a stale status
  // to anyone.
  this.authStatus_ = null;

  if (callback) {
    this.listenerCallbacks_.push(callback);
  }

  if (! this.waitingForResponse_) {
    var onSuccess = goog.bind(this.onCheckSuccess, this);
    var onFailure = goog.bind(this.onCheckFailure, this);

    var getAuthStatus =
        new dkpoints.rpc.commands.AuthCheckCommand(onSuccess,
                                                   onFailure);

    this.waitingForResponse_ = true;
    this.rpc_.execute(getAuthStatus);
  }
};


/**
 * If the server returns successfully, we can use the returned
 * AuthCheckResponse to store the auth state.
 * @param {dkpoints.rpc.commands.AuthCheckResponse} authStatus The auth status
 *    returned by the server.
 */
dkpoints.auth.Auth.prototype.onCheckSuccess = function(authStatus) {
  this.notifyListeners_(authStatus);
};


/**
 * If the server returns with an error, we can diagnose the problem, but should
 * always pass a proper response object back to the caller.
 * @param {goog.events.Event} event used to diagnose the failure.
 */
dkpoints.auth.Auth.prototype.onCheckFailure = function(event) {
  // Create a new AuthCheckResponse and pass it an empty object to initialize
  // it with defaults.
  var authStatus = this.authStatus_ =
                   new dkpoints.rpc.commands.AuthCheckResponse({});

  this.notifyListeners_(authStatus);
};


/**
 * Call all listener callbacks with the newly-acquired authStatus.
 * @param {dkpoints.rpc.commands.AuthCheckResponse} authStatus The authStatus
 *    to pass to any listeners.
 * @private
 */
dkpoints.auth.Auth.prototype.notifyListeners_ = function(authStatus) {
  var listeners = this.listenerCallbacks_;

  this.setAuthStatus_(authStatus);
  this.waitingForResponse_ = false;

  for (var i = 0, len = listeners.length; i < len; ++i) {
    listeners[i](authStatus);
  }

  this.listenerCallbacks_ = [];
};


/**
 * Get the current authentication status without forcing a new check.
 * This must be asynchronous in case it is called before an auth check request
 * to the server has returned.
 *
 * @param {function(dkpoints.rpc.commands.AuthCheckResponse)} callback
 *    The callback to return the authStatus to the user.
 */
dkpoints.auth.Auth.prototype.getAuthStatus = function(callback) {
  if (this.waitingForResponse_) {
    this.listenerCallbacks_.push(callback);
  }
  else {
    callback(this.authStatus_);
  }
};


/**
 * Sets the current auth state.  Should only be called internally.
 * @param {dkpoints.rpc.commands.AuthCheckResponse} authStatus The auth status.
 * @private
 */
dkpoints.auth.Auth.prototype.setAuthStatus_ = function(authStatus) {
  this.authStatus_ = authStatus;
  if (this.nav_) {
    this.nav_.setAuthStatus(authStatus);
  }
  this.menu_.setAuthStatus(authStatus);
};


/**
 * Redirect to a login page which will redirect to the current hash location
 * after authenticating.
 * @param {string} baseURL The base URL for the page.
 * @param {string} referer The page to load after authentication.
 */
dkpoints.auth.Auth.prototype.login = function(baseURL, referer) {
  if (referer === '/auth/login/') {
    referer = '';
  }

  dkpoints.utils.setLocationHref(baseURL + '/apis/auth/login' + referer);
};


/**
 * Perform logout via XHR so the page does not have to be reloaded.  Callback
 * will be null if this is a retry.
 *
 * @param {function()|null} callback Callback provided by the code making the
 *    logout request.
 */
dkpoints.auth.Auth.prototype.logout = function(callback) {
  if (! goog.isNull(callback)) {
    this.listenerCallbacks_.push(callback);
  }

  if (! this.waitingForResponse_) {
    var onSuccess = goog.bind(this.logoutSuccess_, this),
        onFailure = goog.bind(this.logoutFailure_, this),
        logoutCmd =
            new dkpoints.rpc.commands.LogoutCommand(onSuccess, onFailure);

    this.waitingForResponse_ = true;
    this.rpc_.execute(logoutCmd);
  }
};


/**
 * Callback for a successful logout response
 * @param {dkpoints.rpc.commands.AuthCheckResponse} authStatus The new auth
 *    status.
 * @private
 */
dkpoints.auth.Auth.prototype.logoutSuccess_ = function(authStatus) {
  this.notifyListeners_(authStatus);
};


/**
 * Callback for a failed logout request.  I haven't been able to get the live
 * environment to properly redirect to /apis/auth/check and give me an actual
 * response, so let's just check for RPC failure and do a second trip to confirm
 * auth status.
 * @param {goog.net.XhrManager.Event} event The COMPLETE event from the XHR
 *    Manager.
 * @private
 */
dkpoints.auth.Auth.prototype.logoutFailure_ = function(event) {
  // The logout callback is already stored in listenerCallbacks_.
  // checkStatus will call notifyListeners for us.
  this.waitingForResponse_ = false;
  this.checkStatus();
};
